/*
 * test.cpp
 * This file is part of norxx
 *
 * Copyright (C) 2014 - Sebastian Gesemann
 *
 * norxx is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * norxx is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with norxx. If not, see <http://www.gnu.org/licenses/>.
 */

#include <cstring>
#include <cstddef>
#include <iostream>
#include <vector>
#include <exception>
#include <stdexcept>

#include "norx_bits.hpp"
#include "norx_perm.hpp"
#include "norx.hpp"

namespace {

using namespace std;

struct tester
{
	int error_count;
	int test_count;
	int name_padding;

	explicit tester(int np) : error_count(0), test_count(0), name_padding(np) {}

	int ok_count() const { return test_count - error_count; }

	template<class Func>
	void operator()(char const* testname, Func func) {
		putname(testname);
		test_count += 1;
		try {
			int err = func();
			if (err) cout << "FAILED\n";
			else     cout << "OK\n";
			error_count += !!err;
		} catch (std::exception& x) {
			cout << "FAILED via exception: " << x.what() << '\n';
			error_count += 1;
		}
	}

private:
	void putname(char const* testname) const;
};

void tester::putname(char const* testname) const
{
	cout << testname;
	for (int k=std::strlen(testname); k<name_padding; ++k) {
		cout << ' ';
	}
	cout << ": ";
}

template<class T>
std::vector<norx::u8> as_bytes(util::array_ref<const T> data)
{
	const int W = util::bits<T>::value; // word size in bits
	const int opw = W/8;                // octets per word
	std::vector<norx::u8> result (opw*data.size());
	norx::copy(data,util::array_ref<norx::u8>(result));
	return result;
}


// Power-of-F test ...

template<class T>
int test_F_8(const T (&expected)[norx::state_words])
{
	T state[norx::state_words] = {1};
	norx::F<T>(8,state);
	for (unsigned k=0; k<norx::state_words; ++k) {
		if (state[k]!=expected[k]) return 1;
	}
	return 0;
}

const norx::u32 F_8_expected32[] = {
	0xEE830EF5,0xEFEDB52C,0xD9B5DDE0,0x11699703,
	0xA59F827F,0xE7DA769E,0x9ACF9688,0xFE6B4EE6,
	0x2D99EFFF,0xC1F42728,0x1B33FCE4,0x2484C32D,
	0x454DEF51,0x65220E90,0xD8B53023,0x10265221
};

const norx::u64 F_8_expected64[] = {
	0xF51507DD9E95189F,0xAB5E0B1641FAD08F,0x09B7BF70943B60DE,0xE35D03636672DACD,
	0x1D013C731A134DCD,0x850FC95D9CA677C8,0x48D78D3658CBE8D0,0x3898A93514FBF49D,
	0x8849E2B60F59D433,0xA1C7E702A391D4B9,0xC0057990DE07D3EE,0x6BBF9A8B0E6CB108,
	0x7DE67998BA91A9CE,0x68F2B4BC4B8F6A52,0x4EFE2C5711E64647,0x27173B06EFB20807
};


/*
bool not_equal(array_ref<const std::uint32_t> words, norx::const_byte_array_ref bytes)
{
	const unsigned bpw = 4; // bytes per word
	assert(words.size()*bpw == bytes.size());
	for (unsigned i=0; !words.empty(); ++i) {
		if (words.consume_first() !=
		    norx::from_bytes<32>(bytes.consume_first_n(bpw)) )
		{
			return true;
		}
	}
	return false;
}

bool not_equal(array_ref<const std::uint64_t> words, norx::const_byte_array_ref bytes)
{
	const unsigned bpw = 8; // bytes per word
	assert(words.size()*bpw == bytes.size());
	for (unsigned i=0; !words.empty(); ++i) {
		if (words.consume_first() !=
		    norx::from_bytes<64>(bytes.consume_first_n(bpw)) )
		{
			return true;
		}
	}
	return false;
}
*/



// Test vectors for NORX32-4-1 and NORX32-6-1 ...

static const norx::u32 norx32_k[] = {0x00112233,0x44556677,0x8899AABB,0xCCDDEEFF};
static const norx::u32 norx32_n[] = {0xFFFFFFFF,0xFFFFFFFF};
static const norx::u32 norx32_h[] = {0x10000002,0x30000004};
static const norx::u32 norx32_p[] = {0x80000007,0x60000005,0x40000003,0x20000001};

static const norx::u32 norx32_4_1_c[] = {0x1F8F35CD,0xCAFA2A38,0x724C1417,0x228732CA};
static const norx::u32 norx32_4_1_a[] = {0x7702CA8A,0xE8BA5210,0xFD9B73AD,0xC0443A0D};

static const norx::u32 norx32_6_1_c[] = {0xD98EDABA,0x25C18DD9,0xA0CA4C36,0xF73309C6};
static const norx::u32 norx32_6_1_a[] = {0x69872EE5,0x3DAC068C,0xE8D6D8B3,0x0A3D2099};

// Test vectors for NORX64-4-1 and NORX64-6-1 ...

static const norx::u64 norx64_k[] = {
	0x0011223344556677, 0x8899AABBCCDDEEFF, 0xFFEEDDCCBBAA9988, 0x7766554433221100 };
static const norx::u64 norx64_n[] = {
	0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF };
static const norx::u64 norx64_h[] = {
	0x1000000000000002, 0x3000000000000004 };
static const norx::u64 norx64_p[] = {
	0x8000000000000007, 0x6000000000000005, 0x4000000000000003, 0x2000000000000001 };

static const norx::u64 norx64_4_1_c[] = {
	0x1B4DCCFF6779A2C3, 0x865464C856BC4B0C, 0xDADBC58565E1690A, 0x2CB12C0BE9D2F045 };
static const norx::u64 norx64_4_1_a[] = {
	0xD0CE5276FDEC9F6E, 0x33EE64CE5CCA3ABA, 0x1187C05183464BD0, 0xA0915ECA6FAF8757 };

static const norx::u64 norx64_6_1_c[] = {
	0x7223675B69C7A934, 0x1EBAB65233E8DC25, 0xAB660E1BF0F3FEE8, 0x71BE33115B333D6D };
static const norx::u64 norx64_6_1_a[] = {
	0xA05D644CCD2C5887, 0x31DE2501AE4FE789, 0x5C153D99943D29A4, 0x98353A0E38D58A93 };

template<class T>
int full_aead(util::array_ref<const T> key,
              util::array_ref<const T> nonce,
              int R, int D,
              util::array_ref<const T> header,
              util::array_ref<const T> payload,
              util::array_ref<const T> exp_c,
              util::array_ref<const T> exp_a)
{
	const int W = util::bits<T>::value; // word size in bits
	const int opw = W/8;                // octets per word
	const int Abytes = opw * norx::atag_words_default;

	// test encryption mode...
	std::vector<unsigned char> ciphertext (opw * payload.size());
	std::vector<unsigned char> auth_tag (Abytes);
	norx::cipher<W> n;
	n.reset(as_bytes(key),as_bytes(nonce),R,D,Abytes);
	n.feed(norx::domain_header,as_bytes(header));
	n.encrypt(as_bytes(payload),ciphertext);
	n.tag_generate(auth_tag);
	if (!norx::match(as_bytes(exp_c),ciphertext))
		throw std::runtime_error("cipher text does not match test vector");
	if (!norx::match(as_bytes(exp_a),auth_tag))
		throw std::runtime_error("auth tag does not match test vector");

	// test decryption mode...
	std::vector<unsigned char> decrypted (ciphertext.size());
	n.reset(as_bytes(key),as_bytes(nonce),R,D,Abytes);
	n.feed(norx::domain_header,as_bytes(header));
	n.decrypt(ciphertext,decrypted);
	bool c = n.tag_check(auth_tag);
	if (!c)
		throw std::runtime_error("auth tag check failed during decryption");
	if (!norx::match(decrypted,as_bytes(payload)))
		throw std::runtime_error("dec(enc(payload))!=payload");

	// test decryption with intentional error...
	ciphertext[2] ^= 42;
	n.reset(as_bytes(key),as_bytes(nonce),R,D,Abytes);
	n.feed(norx::domain_header,as_bytes(header));
	n.decrypt(ciphertext,decrypted);
	c = n.tag_check(auth_tag);
	if (c)
		throw std::runtime_error("auth tag collision? probably some bug!");
	if (norx::match(decrypted,as_bytes(payload)))
		throw std::runtime_error("dec(enc(payload)^error)==payload is unexpected!");
	return 0;
}

inline int full_aead_32(int R, int D,
                        util::array_ref<const norx::u32> exp_c,
                        util::array_ref<const norx::u32> exp_a)
{
	return full_aead<norx::u32>(
		norx32_k, norx32_n,
		R, D,
		norx32_h, norx32_p,
		exp_c, exp_a );
}

inline int full_aead_64(int R, int D,
                        util::array_ref<const norx::u64> exp_c,
                        util::array_ref<const norx::u64> exp_a)
{
	return full_aead<norx::u64>(
		norx64_k, norx64_n,
		R, D,
		norx64_h, norx64_p,
		exp_c, exp_a );
}

} // anonymous namespace

int main()
{
	tester t (21);
	t("F^8(1,0,...,0) 32bit",[]{return test_F_8(F_8_expected32);});
	t("F^8(1,0,...,0) 64bit",[]{return test_F_8(F_8_expected64);});
	t("full AEAD NORX32-4-1",[]{return full_aead_32(4,1,norx32_4_1_c,norx32_4_1_a);});
	t("full AEAD NORX32-6-1",[]{return full_aead_32(6,1,norx32_6_1_c,norx32_6_1_a);});
	t("full AEAD NORX64-4-1",[]{return full_aead_64(4,1,norx64_4_1_c,norx64_4_1_a);});
	t("full AEAD NORX64-6-1",[]{return full_aead_64(6,1,norx64_6_1_c,norx64_6_1_a);});
	std::cout << t.ok_count() << " of " << t.test_count
	          << " tests were successful.\n";
	return t.error_count ? EXIT_FAILURE : EXIT_SUCCESS;
}

